﻿using System;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using Headsight.BPMN2WF.BPMN.Model;
using Headsight.BPMN2WF.BPMN.Schemas;
using Headsight.BPMN2WF.BPMN.Utils;
using Microsoft.VisualBasic.Activities;

namespace Headsight.BPMN2WF.Transformation.Transformers
{
    /// <summary>
    /// <see cref="IBPMN2WFTransformation"/> implementation transforming 
    /// from BPMN <see cref="tExclusiveGateway"/> to <i>a WF counterpart</i>.
    /// </summary>
    public class BPMN2WFTransformationExclusiveGateway : IBPMN2WFTransformation
    {
        private Sequence _sequence;

        private FlowSwitch<String> _flowSwitch;
        private Flowchart _flowchart;
        private readonly IList<Object> _cache = new List<Object>();
        private FlowNode _flowOut;

        public BPMNTransformationResult Transform(IBPMN2WFTransformationContext ctx, BPMNFlowNode flowNode)
        {
            var processBlock = (BPMNFlowNodesBlock)flowNode;

            _flowSwitch = new FlowSwitch<String> { Expression = new VisualBasicValue<String>("FlowDecisionVariable") };

            _sequence = new Sequence();
            var sequenceFlowStep = new FlowStep { Action = _sequence, Next = _flowSwitch };

            _flowchart = new Flowchart { StartNode = sequenceFlowStep };
            _flowchart.Nodes.Add(sequenceFlowStep);
            _flowchart.Nodes.Add(_flowSwitch);

            _flowchart.Variables.Add(new Variable<String> { Name = "FlowDecisionVariable" });

            if (processBlock.NodeOut != null)
            {
                _flowOut = _flowSwitch;
            }

            // NodeIn is the tExclusiveGateway
            foreach (var each in processBlock.GetNextNodesInFlowFrom(processBlock.FlowNodeIn))
            {
                var eachBranch = CreateBranch2(ctx, processBlock, each);

                var flowStep = eachBranch.Item2.Result.ToFlowStep();

                _flowchart.Nodes.Add(flowStep);

                if (ctx.BPMNProcess.IsDefaultBranchOf(processBlock.FlowNodeIn, each.BPMNFlowNodeOrNodeIn()))
                {
                    _flowSwitch.Default = flowStep;
                }
                else
                {
                    _flowSwitch.Cases.Add(eachBranch.Item1.Item1, flowStep);
                }

                if (each.Equals(processBlock.NodeOut))
                {
                    _flowOut = flowStep;
                }

                TransformRecursive(ctx, processBlock, flowStep, processBlock.GetNextNodesInFlowFrom(each));
            }


            // Add default branch
            if (!ctx.BPMNProcess.HasDefaultBranch(processBlock.FlowNodeIn))
            {
                _flowSwitch.Default = CreateDefaultBranchThrowingException().Result.ToFlowStep();
                _flowchart.Nodes.Add(_flowSwitch.Default);
            }

            return new BPMNTransformationResult(_flowchart, _flowchart.StartNode, _flowOut);
        }

        private Tuple<Tuple<String, String>, BPMNTransformationResult> CreateBranch2(IBPMN2WFTransformationContext ctx, BPMNFlowNodesBlock processBlock, BPMNFlowNode each)
        {
            if (ctx.BPMNProcess.IsDefaultBranchOf(processBlock.FlowNodeIn, each.BPMNFlowNodeOrNodeIn()))
            {
                return CreateDefaultBranch(ctx, processBlock.FlowNodeIn, each);
            }
            
            return CreateBranch(ctx, processBlock.FlowNodeIn, each);
        }

        private void TransformRecursive(IBPMN2WFTransformationContext ctx, BPMNFlowNodesBlock processBlock, FlowStep flowStepParent, IEnumerable<BPMNFlowNode> flowNodes)
        {
            foreach (var each in flowNodes)
            {
                var result = ctx.BPMN2WFTransformations.Get(each).Transform(ctx, each);

                var flowStep = result.Result.ToFlowStep();

                // Simplify here...?!
                flowStepParent.Next = flowStep;

                if (each.Equals(processBlock.NodeOut))
                {
                    _flowOut = flowStep;
                }

                if (!_cache.Contains(each))
                {
                    _cache.Add(each);
                    // simplify...?!

                    _flowchart.Nodes.Add(flowStep);
                }

                TransformRecursive(ctx, processBlock, flowStep, processBlock.GetNextNodesInFlowFrom(each));
            }
        }

        public Tuple<Tuple<String, String>, BPMNTransformationResult> CreateDefaultBranch(IBPMN2WFTransformationContext ctx, BPMNFlowNode gatewayNode, BPMNFlowNode flowNode)
        {
            var resultBranchTransformed = ctx.BPMN2WFTransformations.Get(flowNode).Transform(ctx, flowNode);

            return new Tuple<Tuple<String, String>, BPMNTransformationResult>(null, resultBranchTransformed);
        }

        public BPMNTransformationResult CreateDefaultBranchThrowingException()
        {
            var throwActivity = new Throw
                {
                    Exception =
                        new InArgument<Exception>(
                        new VisualBasicValue<Exception>(
                            "New System.Exception(\"Next flow step could not be determined and BPMN source model has not defined any default branch. BPMN execution semantics require to throw this exception.\")"))
                };

            var result = throwActivity;

            return new BPMNTransformationResult(result, result, result);
        }

        public Tuple<Tuple<String, String>, BPMNTransformationResult> CreateBranch(IBPMN2WFTransformationContext ctx, BPMNFlowNode gatewayNode, BPMNFlowNode flowNode)
        {
            var resultBranchTransformed = ctx.BPMN2WFTransformations.Get(flowNode).Transform(ctx, flowNode);
            var expression = ctx.BPMNProcess.GetExpressionIdAndExpression(gatewayNode, flowNode.BPMNFlowNodeOrNodeIn());

            _sequence.DisplayName = expression.Item1;
            _sequence.Activities.Add(CreateIfActivity(expression));

            return new Tuple<Tuple<String, String>, BPMNTransformationResult>(expression, resultBranchTransformed);
        }

        private static Activity CreateIfActivity(Tuple<String, String> expression)
        {
            var result = new If
                             {
                                 Condition = new VisualBasicValue<bool>(expression.Item2),
                                 Then = new Assign
                                            {
                                                To = new OutArgument<String>
                                                         {
                                                             Expression = new VisualBasicReference<String>
                                                                              {
                                                                                  ExpressionText = "FlowDecisionVariable"
                                                                              }
                                                         },
                                                Value = new InArgument<String>
                                                            {
                                                                Expression =
                                                                    new VisualBasicValue<String>(String.Format("\"{0}\"", expression.Item1))
                                                            }
                                            }
                             };

            return result;
        }
    }
}